#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include "misc.h"
#include "gen_read.h"
#include "token.h"
#include "txt.h"

typedef struct {
  char *name;
  int type;
} TOKEN_INFO;

#define TI_END {"",      TK_NONE}

TOKEN_INFO kwords[] = {
  "and",  TK_AND,
  "else", TK_ELSE,
  "if",   TK_IF,
  "let",  TK_LET,
  "not",  TK_NOT,
  "null", TK_NULL,
  "or",   TK_OR,
  "ref",  TK_REF,
  "then", TK_THEN,
  TI_END
};

// Symbols


typedef struct {
  // Transition character.  Terminate end of list of transitions with
  // trchr == '\0'
  char trchr;
  // -1 ==> no next state
  int next_state;
  // Should the current character under consideration be placed back on the
  // input?
  int putback_chr;
  // Token type to return (if no next state).
  int tk_type;
} TRANSITION;

typedef TRANSITION STATE[255];

#define LIST_END '\0'
#define STOP_STATE -1

STATE sym_transitions[] = {
// State trchr    next_state  putback_chr tk_type
//------------------------------------------------------------------------------
/*0*/ {
        ';',      STOP_STATE, 0,          TK_SEQ,
        '-',      1,          0,          TK_NONE,
        '+',      STOP_STATE, 0,          TK_ADD,
        '*',      STOP_STATE, 0,          TK_MUL,
        '/',      2,          0,          TK_NONE,
        '{',      STOP_STATE, 0,          TK_L_CLOSURE_BRACK,
        '}',      STOP_STATE, 0,          TK_R_CLOSURE_BRACK,
        ',',      STOP_STATE, 0,          TK_TUPLE_APPEND,
        '|',      STOP_STATE, 0,          TK_PATTERN_ALT,
        '(',      STOP_STATE, 0,          TK_L_ROUND_BRACK,
        ')',      STOP_STATE, 0,          TK_R_ROUND_BRACK,
        ':',      3,          0,          TK_NONE,
        '\n',     STOP_STATE, 0,          TK_NEWLINE,
        '_',      STOP_STATE, 0,          TK_ANONYMOUS_VAR,
        LIST_END, STOP_STATE, 0,          TK_NONE
      },
/*1*/ { // "-" or "->"
        //--------------------------------------------------------------
        '>',      STOP_STATE, 0,          TK_RIGHT_ARROW,
        '-',      4,          0,          TK_NONE,
        LIST_END, STOP_STATE, 1,          TK_SUB
      },
/*2*/ { // "/" or "//"
        //--------------------------------------------------------------
        '/',      STOP_STATE, 0,          TK_COMMENT,
        LIST_END, STOP_STATE, 1,          TK_DIV
      },
/*3*/ {
        // ":="
        //--------------------------------------------------------------
        '=',      STOP_STATE, 0,          TK_ASSIGN,
        LIST_END, STOP_STATE, 1,          TK_CONS
      },
/*4*/ {
        '-',      4,          0,          TK_NONE,
        LIST_END, STOP_STATE, 1,          TK_DIV
      },
};

void tk_init(TOKEN *t)
{
  t->type = TK_NONE;
  t->name = NULL;
}

void tk_free(TOKEN *t)
{
  if (t->name) {
    free(t->name);
    t->name = NULL;
  }
  t->type = TK_NONE;
}

static int invisible(char c)
{
  return (' ' == c) || ('\t' == c);
}

static int is_symbol_starter(char c)
{
  int i;
  for (i = 0; sym_transitions[0][i].trchr; ++i) {
    if (c == sym_transitions[0][i].trchr) {
      return 1;
    }
  }
  return 0;
}

#define TN(t) t, #t

struct TK_PRINT_INFO {
  int type;
  char *type_str;
  int print_name;
  int print_num;
  int print_c;
} tk_print_info[] = {
//  Type/type str                   print name?     print num?  print c?
    TN(TK_EOF),                     0,              0,          0,
    TN(TK_TEST),                    0,              0,          1,
    TN(TK_COMMENT),                 0,              0,          0,
    TN(TK_NONE),                    0,              0,          0,
    TN(TK_NEWLINE),                 0,              0,          0,
    TN(TK_ADD),                     0,              0,          0,
    TN(TK_AND),                     0,              0,          0,
    TN(TK_ASSIGN),                  0,              0,          0,
    TN(TK_CONS),                    0,              0,          0,
    TN(TK_OR),                      0,              0,          0,
    TN(TK_MUL),                     0,              0,          0,
    TN(TK_DIV),                     0,              0,          0,
    TN(TK_LT),                      0,              0,          0,
    TN(TK_LE),                      0,              0,          0,
    TN(TK_GT),                      0,              0,          0,
    TN(TK_GE),                      0,              0,          0,
    TN(TK_EQ),                      0,              0,          0,
    TN(TK_SUB),                     0,              0,          0,
    TN(TK_NOT),                     0,              0,          0,
    TN(TK_NEGATE),                  0,              0,          0,
    TN(TK_REF),                     0,              0,          0,
    TN(TK_ANONYMOUS_VAR),           0,              0,          0,
    TN(TK_VARNAME),                 1,              0,          0,
    TN(TK_NUMBER),                  0,              1,          0,
    TN(TK_SYMBOL),                  1,              0,          0,
    TN(TK_L_CLOSURE_BRACK),         0,              0,          0,
    TN(TK_L_ROUND_BRACK),           0,              0,          0,
    TN(TK_R_CLOSURE_BRACK),         0,              0,          0,
    TN(TK_R_ROUND_BRACK),           0,              0,          0,
    TN(TK_SEQ),                     0,              0,          0,
    TN(TK_TUPLE_APPEND),            0,              0,          0,
    TN(TK_ELSE),                    0,              0,          0,
    TN(TK_EQUAL),                   0,              0,          0,
    TN(TK_IF),                      0,              0,          0,
    TN(TK_LET),                     0,              0,          0,
    TN(TK_THEN),                    0,              0,          0,
    TN(TK_RIGHT_ARROW),             0,              0,          0,
    TN(TK_PATTERN_ALT),             0,              0,          0,
    TN(TK_NULL),                    0,              0,          0,
    -1, "",                         0,              0,          0
};

#include "str_tkgrp_macro.h"

void tk_str(TOKEN *t, char *str, size_t n)
{
  int i;
  int np;
  char *strp = str;
  strcpy(str, "");
  for (i = 0; t->type != tk_print_info[i].type &&
         strlen(tk_print_info[i].type_str) > 0;
       ++i) {
    // Empty
  }
  if (strlen(tk_print_info[i].type_str) == 0) {
    snprintf(str, n, "UNKNOWN (%c) (%d)", t->c, t->c);
  } else {
    if (tk_print_info[i].type == t->type) {
      np = snprintf(strp, n, "%s", tk_print_info[i].type_str);
      strp += np;
      n -= np + 1;
    }
    if (tk_print_info[i].print_name) {
      np = snprintf(strp, n, ", (%s)", t->name);
      strp += np;
      n -= np + 1;
    }
    if (tk_print_info[i].print_num) {
      np = snprintf(strp, n, ", %lf", t->num);
      strp += np;
      n -= np + 1;
    }
    if (tk_print_info[i].print_c) {
      np = snprintf(strp, n, ", char=(%c) ascii=(%d)", t->c, t->c);
      strp += np;
      n -= np + 1;
    }
  }
#if 0
  STR_TKGRP(strp, n, np, t->type, TKGRP_INFIX);
  STR_TKGRP(strp, n, np, t->type, TKGRP_UNARY);
  STR_TKGRP(strp, n, np, t->type, TKGRP_SYNTAX);
  STR_TKGRP(strp, n, np, t->type, TKGRP_ATOM);
  STR_TKGRP(strp, n, np, t->type, TKGRP_BRACKET);
  STR_TKGRP(strp, n, np, t->type, TKGRP_LBRACKET);
  STR_TKGRP(strp, n, np, t->type, TKGRP_RBRACKET);
  STR_TKGRP(strp, n, np, t->type, TKGRP_SPECIAL);
  STR_TKGRP(strp, n, np, t->type, TKGRP_KWEXP);
  STR_TKGRP(strp, n, np, t->type, TKGRP_LOWPRI);
  STR_TKGRP(strp, n, np, t->type, TKGRP_HIPRI);
#endif
}

void tk_print(TOKEN *t)
{
  char str[513];
  tk_str(t, str, 512);
  printf("%s\n", str);
}

int tk_next(GEN_READ *r, TOKEN *t)
{
  char c;
  tk_free(t);
  if (gr_eof(r) || !gr_get_char(r, &c)) {
    t->type = TK_EOF;
  } else {
    // This while loop skips sequences of
    //     (<blank>['&' <any>* '\n'])*
    // The '&' character joins two lines together and the
    // newline following it is never seen by the parser.
    while (!gr_eof(r) && (invisible(c) || '&' == c)) {
      if ('&' == c) {
        while (!gr_eof(r) && '\n' != c) {
          gr_get_char(r, &c);
        }
        // Skip '\n'
        if (!gr_eof(r)) {
          gr_get_char(r, &c);
        }
      } else {
        while (!gr_eof(r) && invisible(c)) {
          gr_get_char(r, &c);
        }
      }
    }
    if (gr_eof(r)) {
      t->type = TK_EOF;
    } else if (isupper(c) || islower(c)) {
      // Symbol syntax: <upper>(<upper>|<lower>|<digit>| '_' | '?' | '!')*
      // Varname syntax: <lower>(<upper>|<lower>|<digit>|'_' | '?' | '!')*
      // Reserved words look like varnames.
      TXT *txt = txt_init();
      do {
        txt_append_char(txt, c);
        gr_get_char(r, &c);
      } while (!gr_eof(r) && (isalnum(c) || '_' == c || '?' == c ||
                              '!' == c));
      if (!gr_eof(r)) {
        gr_putback_char(r, c);
      }
      t->name = txt_to_str(txt);
      txt_free(txt);
      if (isupper(t->name[0])) {
        t->type = TK_SYMBOL;
      } else {
        int i;
        for (i = 0; kwords[i].name[0] && strcmp(t->name, kwords[i].name);
             ++i) {
          // EMPTY
        }
        if (kwords[i].name[0]) {
          t->type = kwords[i].type;
        } else {
          t->type = TK_VARNAME;
        }
      }
    } else if ('~' == c || isdigit(c)) {
      // Numeric syntax:
      // ['~']<digit>+['.'<digit>*][('e'|'E')['~']<digit>+]
      int mant_sign = 1, exp_sign = 1;
      int int_part = 0, exp_part = 0;
      double frac_part = 0.0;
      // **SIGN**
      if ('~' == c) {
        mant_sign = -1;
        // Use a harmless value for c to avoid more
        // complicated code involving gr_get_char()
        c = '0';
      }
      // **INTEGER PART**
      do {
        int_part = int_part * 10 + c - '0';
      } while (gr_get_char(r, &c) && isdigit(c));
      // **FRACTIONAL PART**
      if (!gr_eof(r) && '.' == c) {
        double d = 10.0;
        while (gr_get_char(r, &c) && isdigit(c)) {
          frac_part += (c - '0') / d;
          d *= 10;
        }
      }
      // **EXPONENT**
      if (!gr_eof(r) && ('e' == c || 'E' == c)) {
        gr_get_char(r, &c);
        if ('~' == c) {
          exp_sign = -1;
          gr_get_char(r, &c);
        }
        while (!gr_eof(r) && isdigit(c)) {
          exp_part = exp_part * 10 + c - '0';
          gr_get_char(r, &c);
        }
      }
      if (!gr_eof(r)) {
        gr_putback_char(r, c);
      }
      t->type = TK_NUMBER;
      /*
         DBG_VAR(mant_sign, I);
         DBG_VAR(int_part, I);
         DBG_VAR(frac_part, D);
         DBG_VAR(exp_sign, I);
         DBG_VAR(exp_part, I);
       */
      t->num = mant_sign*(int_part + frac_part)*pow(10.0, exp_sign*exp_part);
    } else if (is_symbol_starter(c)) {
      int state = 0, i;
      TXT *symtxt = txt_init();
      txt_append_char(symtxt, c);
      while (STOP_STATE != state) {
        for (i = 0;
             sym_transitions[state][i].trchr != c &&
             sym_transitions[state][i].trchr;
             ++i) {
          // EMPTY
        }
        if (sym_transitions[state][i].putback_chr) {
          gr_putback_char(r, c);
        }
        t->type = sym_transitions[state][i].tk_type;
        state = sym_transitions[state][i].next_state;
        if (STOP_STATE != state) {
          txt_append_char(symtxt, c);
          gr_get_char(r, &c);
        }
      }
      t->name = txt_to_str(symtxt);
    } else {
      // !eof and !invisible(c)
      t->type = TK_TEST;
      t->c = c;
    }
  }
  return t->type;
}

#ifdef TEST_TOKENS

static void init_gr(char test_type, char *arg, GEN_READ *r)
{
  switch (test_type) {
    case 'f':
      if (!gr_open_file(r, arg)) {
        fprintf(stderr, "Can't open %s\n", arg);
        exit(0);
      }
      break;
    case 's':
      if (!gr_open_str(r, arg)) {
        fprintf(stderr, "Can't initialize with string.\n");
        exit(0);
      }
      break;
    case 'r':
      if (!gr_open_rdln(r, arg)) {
        fprintf(stderr, "Can't initialize with readline.\n");
        exit(0);
      }
      break;
    default:
      fprintf(stderr, "Unrecognized input type: %c\n", test_type);
      exit(0);
      break;
  }
}

void main(int argc, char **argv)
{
  GEN_READ r;
  if (argc == 3) {
    TOKEN t;
    init_gr(*argv[1], argv[2], &r);
    tk_init(&t);
    while (tk_next(&r, &t)) {
      tk_print(&t);
      tk_free(&t);
    }
    if (TK_EOF != t.type) {
      printf("Expected TK_EOF but didn't see it.\n");
    } else {
      tk_print(&t);
    }
  } else {
    printf("args: (f <file> | s <string> | r <prompt>)\n");
  }
}

#endif
